#include "DateObject.h"

#include "time.h"

OBJECT_DEFINE_START (DateObject, date_object)

void
date_object_init (DateObject* obj)
{
        object_init (OBJECT (obj));
        object_set_destory_func (obj, date_object_destory);
        obj->date_state = DATE_OBJECT_STATE_START;
        obj->day        = 0;
        obj->month      = 0;
        obj->year       = 0;
}

void
date_object_destruction (DateObject* obj __attribute_maybe_unused__)
{
        return;
}

static DateObjectEvent
date_object_catch_event (char ch)
{
        if (ch == '.')
                return DATE_OBJECT_EVENT_DOT;
        if (ch >= '0' && ch <= '9')
                return DATE_OBJECT_EVENT_NUMBER;
        if (ch == '\0')
                return DATE_OBJECT_EVENT_ENDL;
        else
                return DATE_OBJECT_EVENT_OTHER;
}

int
date_object_compare (const DateObject* obj1, const DateObject* obj2)
{
        unsigned long date1 = 0, date2 = 0;
        if (!obj1 || !obj2) {
                OBJECT_ERROR_TOOL (DateObject,
                                   compare,
                                   "对象为空！obj1<%p>，obj2<%p>\n",
                                   obj1,
                                   obj2);
                return 0;
        }
        date1 = (((long)(obj1->year)) << 16) + (((long)(obj1->month)) << 8) +
                ((long)(obj1->day));
        date2 = (((long)(obj2->year)) << 16) + (((long)(obj2->month)) << 8) +
                ((long)(obj2->day));
        if (date1 < date2)
                return -1;
        else if (date1 > date2)
                return 1;
        else
                return 0;
}

static int
date_object_get_month_type (int month)
{
        switch (month) {
        case 2:
                return 2;
        case 4:
        case 6:
        case 9:
        case 11:
                return 1;
        default:
                return 0;
        }
}

DateObject*
date_object_new_with_today ()
{
        time_t      _timer;
        struct tm*  _today;
        DateObject* obj;
        obj        = date_object_new ();
        _timer     = time (NULL);
        _today     = localtime (&_timer);
        obj->day   = _today->tm_mday;
        obj->month = _today->tm_mon + 1;
        obj->year  = _today->tm_year + 1900;
        return obj;
}

static int
date_object_check_date (DateObject* obj)
{
        bool is_leap_year;
        int  month_type; /*0 大月 1 小月 2 二月*/
        if (!obj) {
                OBJECT_ERROR_TOOL (DateObject, check_date, "对象为空！\n");
                return 1;
        }
        if (obj->year <= 0 || obj->month <= 0 || obj->day <= 0) {
                OBJECT_ERROR_TOOL (DateObject,
                                   check_date,
                                   "日期中有值小于等于 0！\n");
                return 1;
        }
        if (obj->year % 4 == 0)
                is_leap_year = true;
        else
                is_leap_year = false;
        month_type = date_object_get_month_type (obj->month);
        if ((month_type == 0 && obj->day > 31) ||
            (month_type == 1 && obj->day > 30) ||
            (month_type == 2 && ((is_leap_year && obj->day > 29) ||
                                 (!is_leap_year && obj->day > 28)))) {
                OBJECT_ERROR_TOOL (DateObject,
                                   add_days,
                                   "%d 月份没有第 %d 天！\n",
                                   obj->month,
                                   obj->day);
                return 1;
        }
        return 0;
}

static inline void
date_object_add_one_of_day (DateObject* obj,
                            bool*       is_leap_year,
                            int*        month_type)
{
        obj->day += 1;
        if (obj->day <= 28)
                return;
        if ((*month_type == 0) && obj->day > 31) {
                obj->day = 1;
                obj->month += 1;
                goto month_add;
        } else if ((*month_type == 1) && obj->day > 30) {
                obj->day = 1;
                obj->month += 1;
                goto month_add;
        } else if ((*month_type == 2) && ((is_leap_year && obj->day > 29) ||
                                          (!is_leap_year && obj->day > 28))) {
                obj->day = 1;
                obj->month += 1;
                goto month_add;
        }
        return;
month_add:
        if (obj->month > 12) {
                obj->month = 1;
                obj->year += 1;
                goto year_add;
        }
        *month_type = date_object_get_month_type (obj->month);
        return;
year_add:
        *is_leap_year = (obj->year % 4 == 0 ? true : false);
}

void
date_object_add_days (DateObject* obj, int days)
{
        bool is_leap_year;
        int  month_type; /*0 大月 1 小月 2 二月*/
        if (!obj) {
                OBJECT_ERROR_TOOL (DateObject, add_days, "对象为空！\n");
                return;
        }
        if (date_object_check_date (obj)) {
                OBJECT_WARNING_TOOL (DateObject,
                                     add_days,
                                     "日期对象有误，无法进行更改！\n");
                return;
        }
        if (days == 0)
                return;
        is_leap_year = (obj->year % 4 == 0 ? true : false);
        month_type   = date_object_get_month_type (obj->month);
        if (days > 0) {
                for (int i = 0; i < days; ++i)
                        date_object_add_one_of_day (obj,
                                                    &is_leap_year,
                                                    &month_type);
        } else {
                OBJECT_ERROR_TOOL (DateObject,
                                   add_days,
                                   "暂时不支持减天数操作！\n");
                return;
        }
}

int
date_object_get_week_of_other_date (const DateObject* start_day,
                                    const DateObject* other_day)
{
        DateObject _static_tmp_date;
        int        week = 0;

        if (!start_day || !other_day) {
                OBJECT_ERROR_TOOL (DateObject,
                                   get_week_of_today,
                                   "对象为空！start<%p>，other<%p>\n",
                                   start_day,
                                   other_day);
                return 0;
        }

        /* 如果当前时间小于课程表时间，则弹出警告和返回0 */
        if (date_object_compare (other_day, start_day) < 0) {
                OBJECT_WARNING_TOOL (DateObject,
                                     get_week_of_today,
                                     "当前日期小于时间表给定的日期！\n");
                return 0;
        }

        _static_tmp_date.year  = start_day->year;
        _static_tmp_date.month = start_day->month;
        _static_tmp_date.day   = start_day->day;

        do {
                week += 1;
                date_object_add_days (&_static_tmp_date, 7);
        } while (date_object_compare (other_day, &_static_tmp_date) >= 0);

        return week;
}

int
date_object_get_week_of_today (const DateObject* start_day)
{
        int         week;
        DateObject* today_date;

        if (!start_day) {
                OBJECT_ERROR_TOOL (DateObject,
                                   get_week_of_today,
                                   "对象为空！\n");
                return 0;
        }

        today_date = date_object_new_with_today ();
        week       = date_object_get_week_of_other_date (start_day, today_date);
        object_unref (today_date);
        return week;
}

int
date_object_get_day_of_week (const DateObject* start_day,
                             const DateObject* end_day)
{
        DateObject _static_date;
        int        day = 1;
        bool       is_leap_year;
        int        month_type;
        if (!start_day || !end_day) {
                OBJECT_ERROR_TOOL (DateObject,
                                   get_day_of_week,
                                   "对象为空！start<%p>，end<%p>\n",
                                   start_day,
                                   end_day);
                return 0;
        }
        if (date_object_compare (start_day, end_day) > 0) {
                OBJECT_ERROR_TOOL (DateObject,
                                   get_day_of_week,
                                   "暂不支持获取开始日期往前的星期 (start: "
                                   "%d.%d.%d, end: %d.%d.%d)。\n",
                                   start_day->year,
                                   start_day->month,
                                   start_day->day,
                                   end_day->year,
                                   end_day->month,
                                   end_day->day);
                return 0;
        }
        _static_date.year  = start_day->year;
        _static_date.month = start_day->month;
        _static_date.day   = start_day->day;
        is_leap_year       = (_static_date.year % 4 == 0 ? true : false);
        month_type         = date_object_get_month_type (_static_date.month);

        while (date_object_compare (&_static_date, end_day) < 0) {
                date_object_add_one_of_day (&_static_date,
                                            &is_leap_year,
                                            &month_type);
                day += 1;
                if (day > 7)
                        day = 1;
        }
        return day;
}

int
date_object_analysis_date (DateObject* obj, ObjectStringApplication* str)
{
        char            buf[2]        = {'\0', '\0'}, *str_tmp;
        int             refresh_times = 0;
        ObjectString*   tmp;
        DateObjectEvent event;

        if (!obj || !str) {
                OBJECT_ERROR_TOOL (DateObject,
                                   analysis_date,
                                   "传入参数有误！DateObject<%p>"
                                   "，ObjectStringApplication<%p>\n",
                                   obj,
                                   str);
                return 1;
        }
        tmp = object_string_new ();

        object_addref (str);
        object_string_application_rewind (str);
reanalysis:
        if (obj->date_state != DATE_OBJECT_STATE_FAILED) {
                buf[0] = object_string_application_iter (str);
                event  = date_object_catch_event (buf[0]);
        }
        switch (obj->date_state) {
        case DATE_OBJECT_STATE_START: {
                if (event == DATE_OBJECT_EVENT_NUMBER) {
                        object_string_append_string (tmp, buf);
                        obj->date_state = DATE_OBJECT_STATE_RECV;
                } else {
                        OBJECT_ERROR_TOOL (
                                DateObject,
                                analysis_date,
                                "日期字符串 '%s' 中 '\033[01;31m%c\033[0m' "
                                "是非法字符。\n",
                                object_string_get_string (OBJECT_STRING (str)),
                                buf[0]);
                        obj->date_state = DATE_OBJECT_STATE_FAILED;
                }
                break;
        }
        case DATE_OBJECT_STATE_RECV: {
                if (event == DATE_OBJECT_EVENT_NUMBER) {
                        object_string_append_string (tmp, buf);
                } else if (event == DATE_OBJECT_EVENT_ENDL &&
                           refresh_times == 2) {
                        obj->date_state = DATE_OBJECT_STATE_ENDL;
                } else if (event == DATE_OBJECT_EVENT_ENDL &&
                           refresh_times != 2) {
                        OBJECT_ERROR_TOOL (
                                DateObject,
                                analysis_date,
                                "日期字符串 "
                                "'\033[01;31m%s\033[01;32m^~~\033[0m' "
                                "结构不完整。\n",
                                object_string_get_string (OBJECT_STRING (str)));
                        obj->date_state = DATE_OBJECT_STATE_FAILED;
                } else if (event == DATE_OBJECT_EVENT_OTHER) {
                        OBJECT_ERROR_TOOL (
                                DateObject,
                                analysis_date,
                                "日期字符串 '%s' 中 "
                                "'\033[01;31m%c\033[0m' 是非法字符。\n",
                                object_string_get_string (OBJECT_STRING (str)),
                                buf[0]);
                        obj->date_state = DATE_OBJECT_STATE_FAILED;
                } else {
                        /*DATE_OBJECT_EVENT_DOT*/
                        refresh_times += 1;
                        if (refresh_times <= 2)
                                obj->date_state = DATE_OBJECT_STATE_REFRESH;
                        else {
                                OBJECT_ERROR_TOOL (
                                        DateObject,
                                        analysis_date,
                                        "日期字符串 "
                                        "'\033[01;31m%s\033[01;33m(%s)\033[0m' "
                                        "日期字符串尾部多余。\n",
                                        object_string_get_string (
                                                OBJECT_STRING (str)),
                                        (str->ch_ip - 1));
                                obj->date_state = DATE_OBJECT_STATE_FAILED;
                        }
                }
                break;
        }
        case DATE_OBJECT_STATE_REFRESH: {
                if (event == DATE_OBJECT_EVENT_NUMBER) {
                        if (refresh_times == 1) {
                                obj->year = (int)strtol (tmp->charset,
                                                         &str_tmp,
                                                         10);
                        } else if (refresh_times == 2) {
                                obj->month = (int)strtol (tmp->charset,
                                                          &str_tmp,
                                                          10);
                        } else {
                                OBJECT_ERROR_TOOL (
                                        DateObject,
                                        analysis_date,
                                        "日期字符串 "
                                        "'\033[01;31m%s\033[01;33m(%s)\033[0m' "
                                        "日期字符串尾部多余。\n",
                                        object_string_get_string (
                                                OBJECT_STRING (str)),
                                        (str->ch_ip - 1));
                                obj->date_state = DATE_OBJECT_STATE_FAILED;
                                break;
                        }
                        object_string_set_string (tmp, buf);
                        obj->date_state = DATE_OBJECT_STATE_RECV;
                } else if (event == DATE_OBJECT_EVENT_DOT) {
                        OBJECT_ERROR_TOOL (
                                DateObject,
                                analysis_date,
                                "不能用连续的两个 '.' 来分隔日期。\n");
                        obj->date_state = DATE_OBJECT_STATE_FAILED;
                } else if (event == DATE_OBJECT_EVENT_OTHER) {
                        OBJECT_ERROR_TOOL (
                                DateObject,
                                analysis_date,
                                "日期字符串 '%s' 中 "
                                "'\033[01;31m%c\033[0m' 是非法字符。\n",
                                object_string_get_string (OBJECT_STRING (str)),
                                buf[0]);
                        obj->date_state = DATE_OBJECT_STATE_FAILED;
                } else {
                        /*DATE_OBJECT_EVENT_ENDL*/
                        OBJECT_ERROR_TOOL (
                                DateObject,
                                analysis_date,
                                "日期字符串 "
                                "'\033[01;31m%s\033[01;32m^~~\033[0m' "
                                "结构不完整。\n",
                                object_string_get_string (OBJECT_STRING (str)));
                        obj->date_state = DATE_OBJECT_STATE_FAILED;
                }
                break;
        }
        case DATE_OBJECT_STATE_ENDL:
                obj->day = (int)strtol (tmp->charset, &str_tmp, 10);
                if (obj->month <= 0 || obj->month > 12) {
                        OBJECT_ERROR_TOOL (DateObject,
                                           analysis_date,
                                           "\033[01;32m%d.\033[01;31m%d\033[01;"
                                           "32m.%d\033[0m: 月份数值不正确。\n",
                                           obj->year,
                                           obj->month,
                                           obj->day);
                        obj->date_state = DATE_OBJECT_STATE_FAILED;
                        break;
                }
                if (date_object_check_date (obj)) {
                        OBJECT_ERROR_TOOL (DateObject,
                                           analysis_date,
                                           "\033[01;32m%d.%d.\033[01;31m%d\033["
                                           "0m: 天数数值不正确。\n",
                                           obj->year,
                                           obj->month,
                                           obj->day);
                        obj->date_state = DATE_OBJECT_STATE_FAILED;
                        break;
                }
                object_unref (str);
                object_unref (tmp);
                return 0;
        default: /*DATE_OBJECT_STATE_FAILED*/
                OBJECT_ERROR_TOOL (DateObject,
                                   analysis_date,
                                   "解析日期出错！\n");
                object_unref (str);
                object_unref (tmp);
                return 1;
        }
        goto reanalysis;
}

void
date_object_get_monday_of_current_week (const DateObject* start_day,
                                        int               current_week,
                                        DateObject*       will_monday)
{
        if (!start_day || current_week < 1 || !will_monday) {
                OBJECT_ERROR_TOOL (
                        DateObject,
                        get_monday_of_current_week,
                        "传入参数有误！(DateObject*)start_day<%p>，current_"
                        "week:%d(>= 1 ?)，(DateObject*)will_monday<%p>\n",
                        start_day,
                        current_week,
                        will_monday);
                return;
        }
        will_monday->year  = start_day->year;
        will_monday->month = start_day->month;
        will_monday->day   = start_day->day;
        date_object_add_days (will_monday, ((current_week - 1) * 7));
}